﻿/* HEADER
 * ------
 * © 2009 by Salomon Zwecker 
 * modified by:
 * - 
 */
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework;
using Shapes.Geometry;

namespace Shapes.Misc
{
    /// <summary>
    /// a static class which can be used to create special shapes and drawings
    /// </summary>
    public static class GeometryTemplates
    {

        #region LineStrips
        /// <summary>
        /// Creates a star-LineStrip
        /// </summary>
        /// <param name="rayCount">the number of cusps</param>
        /// <param name="innerRadius">the radius of the concave cusps</param>
        /// <param name="outerRadius">the radius of the convex cusps</param>
        /// <param name="rotation">the rotation in radians (0 = east)</param>
        /// <returns>a star LineStrip</returns>
        public static LineStrip CreateStar(int rayCount, float innerRadius, float outerRadius, float rotation)
        {
            List<Vector2> corners = new List<Vector2>();
            float step = MathHelper.TwoPi / (2 * rayCount);

            for (int i = 0; i < rayCount; i++)
            {
                corners.Add(new Vector2((float)Math.Cos(rotation) * outerRadius, (float)Math.Sin(rotation) * outerRadius));
                rotation += step;
                corners.Add(new Vector2((float)Math.Cos(rotation) * innerRadius, (float)Math.Sin(rotation) * innerRadius));
                rotation += step;
            }

            LineStrip ls = new LineStrip(corners.ToArray());
            ls.Close();
            return ls;
        }

        /// <summary>
        /// Creates a convex LineStrip with n edges with the same length describing a circle
        /// </summary>
        /// <param name="cornerCount">the number of corners / edges</param>
        /// <param name="radius">the distance from center of each corner</param>
        /// <param name="rotation">the rotation in radians (0 = east)</param>
        /// <returns>an N-Gon LineStrip</returns>
        public static LineStrip CreateNGon(int cornerCount, float radius, float rotation)
        {
            List<Vector2> corners = new List<Vector2>();
            float step = MathHelper.TwoPi / cornerCount;

            for (int i = 0; i < cornerCount; i++)
            {
                corners.Add(new Vector2((float)Math.Cos(rotation) * radius, (float)Math.Sin(rotation) * radius));
                rotation += step;
            }

            LineStrip ls = new LineStrip(corners.ToArray());
            ls.Close();
            return ls;
        }

        /// <summary>
        /// Creates a star LineStrip where one edge connects two cusps (like a pentagramm, heptagramm, ...)
        /// </summary>
        /// <param name="cornerCount">the number of cusps (must be odd)</param>
        /// <param name="radius">the distance from center of each corner</param>
        /// <param name="rotation">the rotation in radians (0 = east)</param>
        /// <returns>an N-Gramm LineStrip</returns>
        public static LineStrip CreateNGramm(int cornerCount, float radius, float rotation)
        {
            if (cornerCount % 2 == 0)
            {
                throw new Exception("The number of corners of an N-Gramm must be odd.");
            }
            List<Vector2> corners = new List<Vector2>();
            float step = (int)(cornerCount / 2) * MathHelper.TwoPi / cornerCount;

            for (int i = 0; i < cornerCount; i++)
            {
                corners.Add(new Vector2((float)Math.Cos(rotation) * radius, (float)Math.Sin(rotation) * radius));
                rotation += step;
            }

            LineStrip ls = new LineStrip(corners.ToArray());
            ls.Close();
            return ls;
        }

        /// <summary>
        /// Creates an arrow LineStrip
        /// </summary>
        /// <param name="startPoint">the position of the arrow's back</param>
        /// <param name="endPoint">the position to where the arrow is pointing</param>
        /// <param name="barThickness">the thickness of the bar behind the spike</param>
        /// <param name="spikeLength">the length of the spike</param>
        /// <param name="spikeWidth">the width at the back of the spike</param>
        /// <returns>an arrow LineStrip</returns>
        public static LineStrip CreateArrow(Vector2 startPoint, Vector2 endPoint, float barThickness, float spikeLength, float spikeWidth)
        {
            List<Vector2> corners = new List<Vector2>();

            barThickness /= 2;
            spikeWidth /= 2;

            Vector2 normal = Vector2.Normalize(endPoint - startPoint);
            Vector2 perp1 = new Vector2(-normal.Y, normal.X);
            Vector2 perp2 = new Vector2(normal.Y, -normal.X);

            // upper side
            // start
            corners.Add(startPoint + perp1 * barThickness);
            // end at spike
            corners.Add(endPoint - normal * spikeLength + perp1 * barThickness);
            // end of spike
            corners.Add(endPoint - normal * spikeLength + perp1 * spikeWidth);

            // middle
            // spike
            corners.Add(endPoint);
           
            // lower side
            // end of spike
            corners.Add(endPoint - normal * spikeLength + perp2 * spikeWidth);
            // end at spike
            corners.Add(endPoint - normal * spikeLength + perp2 * barThickness);
            // start
            corners.Add(startPoint + perp2 * barThickness);


            LineStrip ls = new LineStrip(corners.ToArray());
            ls.Close();
            return ls;
        }

        /// <summary>
        /// Creates a Koch Snowflake fractal LineStrip
        /// </summary>
        /// <param name="radius">the distance from center of the first 6 corners</param>
        /// <param name="iterations">the number of iterations (the more, the slower. 4 is enough in most cases)</param>
        /// <param name="rotation">the rotation in radians (0 = east)</param>
        /// <returns>a Koch Snowflake fractal polygon</returns>
        public static LineStrip CreateKochSnowflake(float radius, int iterations, float rotation)
        {
            List<Vector2> corners = new List<Vector2>();
            // create Tri
            corners.Add(new Vector2((float)Math.Cos(rotation) * radius, (float)Math.Sin(rotation) * radius));
            corners.Add(new Vector2((float)Math.Cos(rotation + MathHelper.TwoPi / 3) * radius, (float)Math.Sin(rotation + MathHelper.TwoPi / 3) * radius));
            corners.Add(new Vector2((float)Math.Cos(rotation + 2 * MathHelper.TwoPi / 3) * radius, (float)Math.Sin(rotation + 2 * MathHelper.TwoPi / 3) * radius));

            for (int i = 0; i < iterations; i++)
            {
                for (int j = 0; j < corners.Count; j += 4)
                {
                    Vector2 p1 = corners[j];
                    Vector2 p2 = corners[(j + 1) % corners.Count];
                    Vector2 normal = Vector2.Normalize(p2 - p1);
                    Vector2 perp = new Vector2(normal.Y, -normal.X);

                    float lengthOver6 = (p2 - p1).Length() / 6;
                    float lengthOver3 = (p2 - p1).Length() / 3;

                    Vector2 cornerA = p1 + normal * lengthOver3;

                    Vector2 cornerB = p1 + normal * (lengthOver3 + lengthOver6)
                        + perp * (float)Math.Sqrt(lengthOver3 * lengthOver3 - lengthOver6 * lengthOver6);

                    Vector2 cornerC = p2 - normal * lengthOver3;

                    corners.Insert(j + 1, cornerC);
                    corners.Insert(j + 1, cornerB);
                    corners.Insert(j + 1, cornerA);
                }

            }
            LineStrip ls = new LineStrip(corners.ToArray());
            ls.Close();
            return ls;
        }
        #endregion

        #region ShapeComposition's
        /// <summary>
        /// Creates a rectangle with rounded corners.
        /// </summary>
        /// <param name="width">the width of the rectangle</param>
        /// <param name="height">the height of the rectangle</param>
        /// <param name="radius">the radius of one corner-quater-circle</param>
        /// <returns>a ShapeComposition</returns>
        public static ShapeComposition CreateRoundedRect(float width, float height, float radius)
        {
            ShapeComposition comp = new ShapeComposition(new Rect(width, height));

            Rect cut1 = new Rect(radius + 0.9f, radius + 1.9f);
            cut1.Position = new Vector2(-0.9f, -0.9f);
            Rect cut2 = (Rect)cut1.Clone(new Vector2(-0.9f, height - radius));
            Rect cut3 = (Rect)cut1.Clone(new Vector2(width - radius, -0.9f));
            Rect cut4 = (Rect)cut1.Clone(new Vector2(width - radius, height - radius));
            comp.Add(cut1, ShapeComposeFunction.Subtract);
            comp.Add(cut2, ShapeComposeFunction.Subtract);
            comp.Add(cut3, ShapeComposeFunction.Subtract);
            comp.Add(cut4, ShapeComposeFunction.Subtract);

            Ellipse add1 = new Ellipse(new Vector2(radius, radius), radius, radius);
            Ellipse add2 = new Ellipse(new Vector2(radius, height - radius), radius, radius);
            Ellipse add3 = new Ellipse(new Vector2(width - radius, radius), radius, radius);
            Ellipse add4 = new Ellipse(new Vector2(width - radius, height - radius), radius, radius);
            comp.Add(add1, ShapeComposeFunction.Add);
            comp.Add(add2, ShapeComposeFunction.Add);
            comp.Add(add3, ShapeComposeFunction.Add);
            comp.Add(add4, ShapeComposeFunction.Add);

            return comp;
        }

        /// <summary>
        /// Creates a Smiley-ShapeComposition =)
        /// </summary>
        /// <param name="radius">the Radius of the Smiley</param>
        /// <returns>=)</returns>
        public static ShapeComposition CreateSmiley(float radius)
        {
            ShapeComposition comp = new ShapeComposition(new Ellipse(radius, radius));

            Ellipse mouthBack = new Ellipse(new Vector2(radius, radius + radius / 8), (12 * radius / 16), (7 * radius / 16));
            comp.Add(mouthBack, ShapeComposeFunction.Subtract);

            Ellipse mouthTop = new Ellipse(new Vector2(radius, radius + radius / 8 - radius / 7), 12.5f * radius / 16, 8 * radius / 16);
            comp.Add(mouthTop, ShapeComposeFunction.Add);
            

            Ellipse eye = new Ellipse(new Vector2(radius + 9 * radius / 32, radius -radius / 4), radius / 6, 5 * radius / 16);
            comp.Add(eye, ShapeComposeFunction.Subtract);

            Ellipse eye2 = (Ellipse)eye.Clone();
            eye2.Center = new Vector2(radius - 9 * radius / 32, eye2.Center.Y);
            comp.Add(eye2, ShapeComposeFunction.Subtract);


            return comp;
        }

        /// <summary>
        /// Creates a Yin and Yang ShapeComposition
        /// </summary>
        /// <param name="radius">the radius of the yin and yang circle</param>
        /// <param name="innerPointRadius">the radius of the points inside yin or yang</param>
        /// <param name="outlineThickness">the thickness of the ring around the symbol</param>
        /// <returns>a Yin and Yang ShapeComposition</returns>
        public static ShapeComposition CreateYinYang(float radius, float innerPointRadius, float outlineThickness)
        {
            Ellipse basic = new Ellipse(radius + outlineThickness, radius + outlineThickness);

            ShapeComposition comp = new ShapeComposition(basic);

            comp.Add(new Ellipse(new Vector2(outlineThickness + radius), radius, radius), ShapeComposeFunction.Subtract);
            Rect fill = new Rect(new Vector2(0, outlineThickness), radius + outlineThickness, 2 * radius);
            comp.Add(fill, ShapeComposeFunction.Add);

            // Yin
            Ellipse yin = new Ellipse(new Vector2(outlineThickness + radius, outlineThickness + 3 * radius / 2), radius / 2, radius / 2);
            comp.Add(yin, ShapeComposeFunction.Add);

            Ellipse yinPoint = new Ellipse(yin.Center, innerPointRadius, innerPointRadius);
            comp.Add(yinPoint, ShapeComposeFunction.Subtract);

            // Yang
            Ellipse yang = (Ellipse)yin.Clone();
            yang.Center = new Vector2(outlineThickness + radius, outlineThickness + radius / 2);
            comp.Add(yang, ShapeComposeFunction.Subtract);

            Ellipse yangPoint = (Ellipse)yinPoint.Clone();
            yangPoint.Center = yang.Center;
            comp.Add(yangPoint, ShapeComposeFunction.Add);

            comp.Add((Ellipse)basic.Clone(), ShapeComposeFunction.Mask);

            return comp;
        }
        #endregion
    }
}
